/**
 * User: alesj
 * Date: 2005.6.3
 * Time: 10:34:52
 * 
 * (C) Genera Lynx d.o.o.
 */

package com.generalynx.common.web.tags;

import com.generalynx.common.data.IEntity;
import com.generalynx.common.resources.Resources;
import com.generalynx.ecos.data.dao.IBasicDAO;
import com.generalynx.ecos.data.dao.IBasicDAOAware;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hibernate.EntityMode;
import org.hibernate.Hibernate;
import org.hibernate.SessionFactory;
import org.hibernate.MappingException;
import org.hibernate.cfg.Configuration;
import org.hibernate.mapping.*;
import org.hibernate.metadata.ClassMetadata;
import org.hibernate.type.AbstractComponentType;
import org.hibernate.type.Type;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.context.MessageSource;
import org.springframework.context.NoSuchMessageException;
import org.springframework.context.i18n.LocaleContextHolder;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Iterator;

public class ADEContextFactoryImpl
        implements ADEContextFactory,
        IBasicDAOAware,
        //InitializingBean,
        BeanFactoryAware {

    private static final Log logger = LogFactory.getLog(ADEContextFactoryImpl.class);

    private static final String LABEL = "label";
    private static final String INCLUDE = "include";
    private static final String RENDERER = "renderer";
    private static final String ORDER = "order";

    private SessionFactory sessionFactory;
    private Configuration configuration;
    private IBasicDAO basicDAO;
    private BeanFactory beanFactory;

    private EntityMode entityMode = EntityMode.POJO;

    /*
    public void afterPropertiesSet() throws Exception {
        configuration = ((LocalSessionFactoryBean)getBeanFactory()
                .getBean("&" + BeanConst.sessionFactory))
                .getConfiguration();
    }
    */

    private MetaDataHolder toMetaDataHolder(List metaDataList) {
        int size = metaDataList.size();
        String[] labels = new String[size];
        String[] props = new String[size];
        Type[] types = new Type[size];
        Object[] values = new Object[size];
        boolean[] includes = new boolean[size];
        SelectRenderer[] renderers = new SelectRenderer[size];

        for(int i = 0; i < size; i++) {
            MetaDataElement mde = (MetaDataElement)metaDataList.get(i);
            labels[i] = mde.getLabelName();
            props[i] = mde.getPropertName();
            types[i] = mde.getType();
            values[i] = mde.getValue();
            includes[i] = mde.isInclude();
            renderers[i] = mde.getRenderer();
        }

        return new MetaDataHolder(labels, props, types, values, includes, renderers);
    }

    private String getLabel(MetaAttributable ma) {
        MetaAttribute attribute = ma.getMetaAttribute(LABEL);
        if (attribute != null) {
            String keyCode = attribute.getValue();
            MessageSource ms = (MessageSource)beanFactory.getBean("messageSource");
            try {
                return ms.getMessage(keyCode, new Object[]{}, LocaleContextHolder.getLocale());
            } catch (NoSuchMessageException nsme) {
                try {
                    return Resources.getString(keyCode.toUpperCase());
                } catch (Exception e) {
                    return null;
                }
            }
        } else {
            return null;
        }
    }

    private boolean isIncluded(MetaAttributable ma) {
        MetaAttribute attribute = ma.getMetaAttribute(INCLUDE);
        return attribute == null || Boolean.valueOf(attribute.getValue()).booleanValue();
    }

    private SelectRenderer getSelectRenderer(MetaAttributable ma) {
        MetaAttribute attribute = ma.getMetaAttribute(RENDERER);
        if (attribute != null) {
            String name = attribute.getValue();
            try {
                return (SelectRenderer)getBeanFactory().getBean(name);
            } catch (Exception e) {
                try {
                    return (SelectRenderer)Class.forName(name).newInstance();
                } catch (Exception e1) {
                    logger.warn("Expecting renderer by class name: " + name);
                }
            }
        }
        return null;
    }

    private int getOrder(MetaAttributable ma) {
        MetaAttribute attribute = ma.getMetaAttribute(ORDER);
        return attribute != null ? Integer.parseInt(attribute.getValue()) : Integer.MAX_VALUE;
    }

    public MetaDataHolder getMetaData(IEntity entity) {
        Class entityClass = Hibernate.getClass(entity);
        //add new or edit old
        IEntity nonProxyEntity = (entity.getId() == 0) ?
                entity :
                getBasicDAO().getEntity(entityClass, entity.getId());

        ClassMetadata cmd = getSessionFactory().getClassMetadata(entityClass);
        PersistentClass persistentClass = getConfiguration().getClassMapping(entityClass.getName());

        String[] properties = cmd.getPropertyNames();
        List metaDataList = new ArrayList();
        for (int i = 0; i < properties.length; i++) {
            if (cmd.getVersionProperty() != i) { //exclude version
                String propertyName = properties[i];
                Property property = persistentClass.getProperty(propertyName);
                Type type = cmd.getPropertyType(propertyName);
                Object value = cmd.getPropertyValue(nonProxyEntity, propertyName, getEntityMode());
                String labelName = getLabel(property);
                boolean include = isIncluded(property);
                SelectRenderer renderer = getSelectRenderer(property);
                if (renderer != null && renderer instanceof AwareSelectRenderer) {
                    ((AwareSelectRenderer)renderer).setOwnerAndPropertyValue(nonProxyEntity, property, value);
                }
                int order = getOrder(property);
                metaDataList.add(new MetaDataElement(labelName, propertyName, type, value, include, renderer, order));
            }
        }
        Collections.sort(metaDataList);
        return toMetaDataHolder(metaDataList);
    }

    public MetaDataHolder getComponentMetaData(IEntity entity,
                                               String prop,
                                               AbstractComponentType act,
                                               Object component,
                                               String prefix) {

        Class entityClass = Hibernate.getClass(entity);
        PersistentClass persistentClass = getConfiguration().getClassMapping(entityClass.getName());

        String pfx = (prefix != null ? (prefix + ".") : "") + prop;
        String[] prefixes = pfx.split("\\.");
        Property parentProperty = persistentClass.getProperty(prefixes[0]);
        for(int i = 1; i < prefixes.length; i++) {
            parentProperty = findProperty(getChildProperties(parentProperty), prefixes[i]);
        }
        Iterator propsIter = getChildProperties(parentProperty);
        List props = new ArrayList();
        while (propsIter.hasNext()) {
            props.add(propsIter.next());
        }

        String[] properties = act.getPropertyNames();
        Type[] types = act.getSubtypes();
        Object[] values = act.getPropertyValues(component, getEntityMode());

        List metaDataList = new ArrayList();
        for (int i = 0; i < properties.length; i++) {
            String propertyName = properties[i];
            Type type = types[i];
            Object value = values[i];
            Property property = findProperty(props, propertyName);
            String label = getLabel(property);
            boolean included = isIncluded(property);
            SelectRenderer renderer = getSelectRenderer(property);
            int order = getOrder(property);
            metaDataList.add(new MetaDataElement(label, propertyName, type, value, included, renderer, order));
        }
        return toMetaDataHolder(metaDataList);
    }

    private Iterator getChildProperties(Property parentProperty) {
        if (!parentProperty.isComposite()) {
            throw new IllegalArgumentException("Should be comoponent!?");
        } else {
            Component componentValue = (Component)parentProperty.getValue();
            return componentValue.getPropertyIterator();
        }
    }

    private Property findProperty(List props, String propertyName) {
        return findProperty(props.iterator(), propertyName);
    }

    private Property findProperty(Iterator propsIter, String propertyName) {
        while (propsIter.hasNext()) {
            Property p = (Property)propsIter.next();
            if (propertyName.equals(p.getName())) {
                return p;
            }
        }
        throw new MappingException("Property not found: " + propertyName);
    }

    // ------------------------------------

    public SessionFactory getSessionFactory() {
        return sessionFactory;
    }

    public void setSessionFactory(SessionFactory sessionFactory) {
        this.sessionFactory = sessionFactory;
    }

    public Configuration getConfiguration() {
        return configuration;
    }

    public void setConfiguration(Configuration configuration) {
        this.configuration = configuration;
    }

    public IBasicDAO getBasicDAO() {
        return basicDAO;
    }

    public void setBasicDAO(IBasicDAO basicDAO) {
        this.basicDAO = basicDAO;
    }

    public EntityMode getEntityMode() {
        return entityMode;
    }

    public void setEntityMode(EntityMode entityMode) {
        this.entityMode = entityMode;
    }

    public BeanFactory getBeanFactory() {
        return beanFactory;
    }

    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        this.beanFactory = beanFactory;
    }

}
